﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Engine.Sgml.Validation;
using Engine.Sgml.Schema;

namespace Engine.Sgml.Test.Util {
    class ModelGroupsMatchGenerator {
        private DModelGroupsGenerator modelGroupsGenerator;
        private SElement ancestor;
        private Random random;
        private ModelGroupsMatch result;
        private HwStack<ModelGroupsMatch> matchPool;

        public ModelGroupsMatchGenerator() {
            ModelGroupsElementConfiguration = new DElementConfiguration();
            ModelGroupsConfiguration = new DModelGroupsConfiguration(ModelGroupsElementConfiguration);
            Configuration = new ModelGroupsMatchConfiguration();
            Statistics = new ModelGroupsMatchGeneratorStatistics();
            modelGroupsGenerator = new DModelGroupsGenerator();
            random = new Random();
            ancestor = new SElement("element", SgmlNodeType.Element);
            result = new ModelGroupsMatch();
            matchPool = new HwStack<ModelGroupsMatch>(7, () => new ModelGroupsMatch());
            }

        public ModelGroupsMatchConfiguration Configuration { get; private set; }

        public DElementConfiguration ModelGroupsElementConfiguration { get; private set; }

        public DModelGroupsConfiguration ModelGroupsConfiguration { get; private set; }

        public ModelGroupsMatchGeneratorStatistics Statistics { get; private set; }

        public ModelGroupsMatch Generate() {
            Statistics.Reset();
            ModelGroupsMatch match = GenerateInnerMatch(null);
            GenerateInnerMatches(ref match);
            return match;
            }

        private ModelGroupsMatch GenerateInnerMatch(ModelGroupsMatch parent) {
            ModelGroupsMatch match = null;
            if (parent == null)
                match = this.result;
            else
                match = matchPool.Push();
            ModelGroupsConfiguration.Occurrance = GenerateModelGroupsOccurance();
            match.Reset(modelGroupsGenerator.Generate(ModelGroupsConfiguration), ancestor, parent);
            match.Type = GenerateAccordanceType();
            Statistics.Collect(match);
            return match;
            }

        private void GenerateInnerMatches(ref ModelGroupsMatch match) {
            if (Statistics.TotalMatchCount >= Configuration.MaxMatchCount)
                return;
            if (InnerMatchGenerated && GetMatchDepth(match) < Configuration.MaxDepth) {
                int matchCount = GenerateInnerMatchCount();
                if (match.Type == AccordanceTypes.Inconsistent) {
                    for (int i = 0; i < matchCount; i++) {
                        ModelGroupsMatch innerMatch = GenerateInnerMatch(match);
                        if (innerMatch.Type != AccordanceTypes.Inconsistent) {
                            Statistics.Change(innerMatch, false);
                            innerMatch.Type = AccordanceTypes.Inconsistent;
                            }
                        else
                            Statistics.Change(innerMatch, false);
                        }
                    }
                else {
                    for (int i = 0; i < matchCount; i++) {
                        ModelGroupsMatch innerMatch = GenerateInnerMatch(match);
                        GenerateRepetitiveMatches(ref innerMatch);
                        }
                    }
                ValidateInnerMatches(ref match);
                for (int i = 0; i < match.Captures.Count; i++) {
                    ModelGroupsMatch innerMatch = match.Captures[i].Match;
                    if (innerMatch != null)
                        GenerateInnerMatches(ref innerMatch);
                    }
                }
            }

        private void ValidateInnerMatches(ref ModelGroupsMatch match) {
            switch (match.Type) {
                case AccordanceTypes.Inconsistent:
                    foreach (ModelGroupsMatch inner in match.Captures) {
                        if (inner.HasRepetitive)
                            throw new InvalidOperationException("The inconsistent match contains repetitive match objects.");
                        if (inner.Type != AccordanceTypes.Inconsistent) {
                            Statistics.Change(inner, false);
                            inner.Type = AccordanceTypes.Inconsistent;
                            Statistics.Change(inner, true);
                            throw new InvalidOperationException("The inconsistent match contains non inconsistent matches.");
                            }
                        }
                    break;
                case AccordanceTypes.Partial:
                    if (!match.InnerMatches.Any(m => m.Type == AccordanceTypes.Partial)) {
                        ModelGroupsMatch inner = match.InnerMatches[random.Next(0, match.InnerMatches.Count)];
                        Statistics.Change(inner, false);
                        inner.Type = AccordanceTypes.Partial;
                        Statistics.Change(inner, true);
                        }
                    break;
                case AccordanceTypes.Full:
                    if (!match.InnerMatches.Any(m => m.Type == AccordanceTypes.Full)) {
                        ModelGroupsMatch inner = match.InnerMatches[random.Next(0, match.InnerMatches.Count)];
                        Statistics.Change(inner, false);
                        inner.Type = AccordanceTypes.Full;
                        Statistics.Change(inner, true);
                        }
                    break;
                default:
                    throw new InvalidOperationException();
                }
            }

        private void GenerateRepetitiveMatches(ref ModelGroupsMatch match) {
            if (match.Type == AccordanceTypes.Inconsistent)
                return;
            if (Statistics.TotalMatchCount >= Configuration.MaxMatchCount)
                return;
            if (match.Model.CanRecur() && RepetitiveMatchGenerated) {
                int matchCount = GenerateRepetitiveMatchCount();
                for (int i = 0; i < matchCount; i++) {
                    ModelGroupsMatch repetitiveMatch = GenerateInnerMatch(match);
                    GenerateInnerMatches(ref repetitiveMatch);
                    }
                }
            }

        private int GetMatchDepth(ModelGroupsMatch match) {
            int depth = 0;
            ModelGroupsMatch parent = match.Parent;
            while (parent != null) {
                depth++;
                parent = parent.Parent;
                }
            return depth;
            }

        private int GenerateInnerMatchCount() {
            return random.Next(1, Configuration.MaxInnerMatchCount);
            }

        private int GenerateRepetitiveMatchCount() {
            return random.Next(1, Configuration.MaxRepetitiveMatchCount);
            }

        private DElementOccurrence GenerateModelGroupsOccurance() {
            return (DElementOccurrence)random.Next(4);
            }

        private AccordanceTypes GenerateAccordanceType() {
            return (AccordanceTypes)random.Next(0, 3);
            }

        private AccordanceTypes GenerateRepetitiveAccordanceType() {
            return (AccordanceTypes)random.Next(1, 3);
            }

        private bool RepetitiveMatchGenerated {
            get {
                switch (random.Next(0, 2)) {
                    case 0:
                        return false;
                    case 1:
                        return true;
                    default:
                        throw new InvalidOperationException();
                    }
                }
            }

        private bool InnerMatchGenerated {
            get {
                switch (random.Next(0, 2)) {
                    case 0:
                        return false;
                    case 1:
                        return true;
                    default:
                        throw new InvalidOperationException();
                    }
                }
            }
        }
    }
